package gogsci

import (
	"bytes"
	"fmt"
	"github.com/pkg/errors"
	"gopkg.in/yaml.v2"
	"strings"
	"sync"
)

type Ci struct {
	StageNames []string          `yaml:"stages"`
	Variables  map[string]string `yaml:"variables"`
	Stages     map[string]Stage  `yaml:"tasks"`

	payload  *PushPayload
	repoInfo *RepoInfo
}

func NewCi(in []byte, payload *PushPayload) (*Ci, error) {

	ci := new(Ci)
	err := yaml.Unmarshal(in, ci)

	if err != nil {
		return nil, err
	}
	ci.payload = payload
	return ci, nil
}

func (ci *Ci) analyse() {
	info := new(RepoInfo)
	if ci.payload.RefType != "" {
		info.IsTag = true
		info.TagName = ci.payload.Ref
	}

	if ci.payload.Release != nil {
		info.IsRelease = true
		info.TagName = ci.payload.Release.TagName
	}

	arr := strings.Split(ci.payload.Ref, "/")
	if len(arr) == 3 && ci.payload.Ref != "" && ci.payload.Commits != nil && len(ci.payload.Commits) != 0 {
		info.IsBranch = true
		info.BranchName = arr[2]
	}

	ci.repoInfo = info
}

func (ci *Ci) Run(env Environment) error {

	var buf = bytes.NewBuffer(nil)

	var canRunners = make(map[string][]*Stage)

	for name, stage := range ci.Stages {
		if !ci.ShouldRunStage(&stage) {
			continue
		}
		stage.Env = env
		stage.Name = name

		if _, ok := canRunners[stage.Stage]; !ok {
			canRunners[stage.Stage] = make([]*Stage, 0)
		}

		canRunners[stage.Stage] = append(canRunners[stage.Stage], &stage)
	}

	var er error
	var mu sync.Mutex

	for stageName, stages := range canRunners {
		var wg = sync.WaitGroup{}
		var shouldContinue = true
		for _, stage := range stages {
			wg.Add(1)
			go func(s *Stage) {
				defer wg.Done()

				buf.WriteString(fmt.Sprintf("stage %s start: %s", stageName, s.Name))

				if runner, ok := registeredStage[s.Type]; !ok {
					buf.WriteString(fmt.Sprintf("stage %s not support for %s", stageName, s.Name))
					shouldContinue = false
				} else {
					err := runner.RunStage(s, buf)
					if err != nil {
						mu.Lock()
						if er != nil {
							er = errors.Wrap(er, err.Error())
						} else {
							er = err
						}
						shouldContinue = false
						mu.Unlock()
					}
				}
			}(stage)
		}
		wg.Wait()

		if !shouldContinue {
			break
		}
	}

	fmt.Println(buf.String())

	return er
}

func (ci *Ci) ShouldRunStage(stage *Stage) bool {

	for _, c := range ci.StageNames {
		if c != stage.Stage {
			continue
		}

		if len(stage.Only) == 0 {
			return true
		}

		for _, v := range stage.Only {
			if strings.Contains(v, ":") { // :master
				branch := v[1:]
				if ci.repoInfo.IsBranch && ci.repoInfo.BranchName == branch {
					return true
				}
			}

			if v == "tags" && ci.repoInfo.IsTag {
				return true
			}

			if v == "release" && ci.repoInfo.IsRelease {
				return true
			}
		}
	}

	return false
}

type Stage struct {
	Name      string            `yaml:"-"`
	Type      string            `yaml:"type"` // docker || local
	Image     string            `yaml:"image"`
	ForcePull bool              `yaml:"force_pull"`
	Stage     string            `yaml:"stage"`
	Script    []string          `yaml:"script"`
	Share     []string          `yaml:"share"`
	Only      []string          `yaml:"only"`
	Ports     []string          `yaml:"ports"`
	Env       map[string]string `yaml:"-"`
	Daemon    bool              `yaml:"daemon"`
	Debug     bool              `yaml:"debug"`
	Volumes   []string          `yaml:"volumes"`
}

type RepoInfo struct {
	IsTag          bool
	IsRelease      bool
	IsBranch       bool
	BranchName     string
	TagName        string
	IsMergeRequest bool
}
